package com.sijiao.difflog.util;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.sijiao.difflog.annotation.LogPrimary;
import com.sijiao.difflog.constants.ClassTypeEnum;
import com.sijiao.difflog.constants.OpTypeEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

public class BeanUtil {
    private static final Logger logger = LoggerFactory.getLogger(BeanUtil.class);
    private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * {
     * "className":"xxx",
     * "id":"1",
     * "bizName":"product",
     * "type":"update"
     * "bizModel":{
     * // snapshot
     * },
     * "opDetail":["insert","delete","name","fans","favor","age"]
     * "diff":{
     * "name":{
     * "origin":"E14",
     * "result":“E14Pro”
     * },
     * "fans":{
     * "update":[
     * {"id":222,
     * "favor":{
     * "origin":"黑色",
     * "result":"银色"
     * } ,
     * "age":{
     * "origin":"15",
     * "result":"30"
     * }
     * }
     * ]
     * }
     * }
     * }
     *
     * @param original
     * @param result
     * @return
     */
    public static String createLogJson(String bizName, Object original, Object result) {
        if (original == null && result == null) {
            return null;
        }
        JsonObject jsonObject = new JsonObject();
        jsonObject.add("bizName", new JsonPrimitive(bizName));
        jsonObject.addProperty("opTime", SIMPLE_DATE_FORMAT.format(new Date()));
        //TODO opUser
        //TODO opScene
        //TODO 收集变更过的字段
        return createDiffObject(jsonObject, original, result).toString();
    }

    private static JsonElement createDiffObject(JsonObject jsonObject, Object original, Object result) {

        Class<?> clazz;
        if (original == null) {
            clazz = result.getClass();
        } else {
            clazz = original.getClass();
        }
        String primaryValue;
        jsonObject.add("className", new JsonPrimitive(clazz.getName()));
        if (original == null) {
            jsonObject.add("opType", new JsonPrimitive(OpTypeEnum.insert.name()));
            primaryValue = findPrimaryValue(result);
        } else if (result == null) {
            jsonObject.add("opType", new JsonPrimitive(OpTypeEnum.delete.name()));
            primaryValue = findPrimaryValue(original);
        } else {
            jsonObject.add("opType", new JsonPrimitive(OpTypeEnum.update.name()));
            primaryValue = findPrimaryValue(result);
        }
        jsonObject.add("id", new JsonPrimitive(primaryValue));
        List<Field> fields = getClassFields(clazz);
        JsonObject diffObject = new JsonObject();
        jsonObject.add("diff", diffObject);
        for (Field field : fields) {
            if (field.getAnnotation(LogPrimary.class) != null) {
                continue;
            }
            Object originalValue = getValue(original, field);
            Object resultValue = getValue(result, field);

            JsonElement jsonElement = createObject(originalValue, resultValue);
            if (jsonElement != null) {
                diffObject.add(field.getName(), jsonElement);
            }
        }
        return jsonObject;
    }

    public static List<Field> getClassFields(Class<?> clazz) {
        if (clazz == Object.class) {
            return Collections.EMPTY_LIST;
        }
        List<Field> list = new LinkedList<>();
        Field[] fields = clazz.getDeclaredFields();
        list.addAll(Arrays.stream(fields).collect(Collectors.toList()));
        list.addAll(getClassFields(clazz.getSuperclass()));
        return list;
    }

    private static Object getValue(Object instance, Field field) {
        if (instance != null) {
            try {
                Field instanceField = findRealField(instance.getClass(), field.getName());
                if (instanceField != null) {
                    instanceField.setAccessible(true);
                    return instanceField.get(instance);
                }
            } catch (IllegalAccessException e) {
                logger.error("{} had no access for filed {}", instance.getClass().getName(), field.getName());
            }
        }
        return null;
    }

    private static Field findRealField(Class<?> clazz, String fieldName) {
        if (clazz == Object.class) {
            return null;
        }
        Field[] fields = clazz.getDeclaredFields();
        for (Field realField : fields) {
            if (realField.getName().equals(fieldName)) {
                return realField;
            }
        }
        return findRealField(clazz.getSuperclass(), fieldName);
    }

    //V2.0
    private static JsonElement createObject(Object original, Object result) {
        if (result == null && original == null) {
            return null;
        }
        ClassTypeEnum classType = original == null ? ClassUtil.getClassType(result) : ClassUtil.getClassType(original);
        if (classType == ClassTypeEnum.none) {
            return null;
        }
        if (classType == ClassTypeEnum.primitive || classType == ClassTypeEnum.string) {
            return generatePrimitive(original, result);
        } else if (classType == ClassTypeEnum.object) {
            return generateObject(original, result);
        } else if (classType == ClassTypeEnum.list) {
            return generateArray(original, result);
        } else if (classType == ClassTypeEnum.map) {
            return generateMap(original, result);
        }
        return null;
    }

    private static JsonElement generateMap(Object original, Object result) {
        Map<?, ?> originalMap = (Map<?, ?>) original;
        Map<?, ?> resultMap = (Map<?, ?>) result;
        if (originalMap == resultMap) {
            return null;
        }
        JsonObject arrayJsonObj = new JsonObject();
        JsonArray insertArray = new JsonArray();
        JsonArray updateArray = new JsonArray();
        JsonArray deleteArray = new JsonArray();
        if (originalMap == null || originalMap.isEmpty()) {
            for (Map.Entry<?, ?> entry : resultMap.entrySet()) {
                assembleMap(String.valueOf(entry.getKey()), entry.getValue(), insertArray);
            }
        } else if (resultMap == null || resultMap.isEmpty()) {
            for (Map.Entry<?, ?> entry : originalMap.entrySet()) {
                assembleMap(String.valueOf(entry.getKey()), entry.getValue(), deleteArray);
            }
        } else {
            for (Map.Entry<?, ?> originalEntry : originalMap.entrySet()) {
                String primaryValue = originalEntry.getKey().toString();
                Object originalValue = originalEntry.getValue();
                Object resultValue = resultMap.get(primaryValue);
                if (resultValue == null) {
                    assembleMap(primaryValue, originalValue, deleteArray);
                } else {
                    JsonObject object = (JsonObject) createObject(originalValue, resultValue);
                    object.add("id", new JsonPrimitive(primaryValue));
                    updateArray.add(object);
                }
            }
            for (Map.Entry<?, ?> resultEntry : resultMap.entrySet()) {
                String primaryValue = resultEntry.getKey().toString();
                Object resultValue = resultEntry.getValue();
                Object originalValue = originalMap.get(primaryValue);
                if (originalValue == null) {
                    assembleMap(primaryValue, resultValue, insertArray);
                } else {
                    JsonObject object = (JsonObject) createObject(original, original);
                    object.add("id", new JsonPrimitive(primaryValue));
                    updateArray.add(object);
                }
            }
        }
        if (!insertArray.isEmpty()) {
            arrayJsonObj.add("insert", insertArray);
        }
        if (!updateArray.isEmpty()) {
            arrayJsonObj.add("update", updateArray);
        }
        if (!deleteArray.isEmpty()) {
            arrayJsonObj.add("delete", deleteArray);
        }
        return arrayJsonObj;
    }


    private static void assembleMap(String id, Object value, JsonArray insertArray) {
        JsonObject object = (JsonObject) createObject(null, value);
        object.add("id", new JsonPrimitive(id));
        insertArray.add(object);
    }

    private static JsonObject generateArray(Object original, Object result) {
        Collection<?> originalCollection = (Collection<?>) original;
        Collection<?> resultCollection = (Collection<?>) result;
        if (original == result) {
            return null;
        }

        JsonObject arrayJsonObj = new JsonObject();
        JsonArray insertArray = new JsonArray();
        JsonArray updateArray = new JsonArray();
        JsonArray deleteArray = new JsonArray();

        if (originalCollection == null || originalCollection.isEmpty()) {
            assembleInsertArray(resultCollection, insertArray);
        } else if (resultCollection == null || resultCollection.isEmpty()) {
            assembleDeleteArray(originalCollection, deleteArray);
        } else {
            //取到交集
            Collection<?> originalRetainCollection = originalCollection.stream().filter(resultCollection::contains).collect(Collectors.toList());
            Collection<?> resultRetainCollection = resultCollection.stream().filter(originalRetainCollection::contains).collect(Collectors.toList());

            if (!originalRetainCollection.isEmpty()) {
                originalCollection.removeAll(originalRetainCollection);
                resultCollection.removeAll(originalRetainCollection);
            }
            assembleInsertArray(resultCollection, insertArray);
            assembleDeleteArray(originalCollection, deleteArray);
            for (Object originalItem : originalRetainCollection) {
                ClassTypeEnum childItemClassType = ClassUtil.getClassType(originalItem);
                if (childItemClassType == ClassTypeEnum.list) {
                    deleteArray.add(generateArray(null, originalItem));
                } else if (childItemClassType == ClassTypeEnum.map) {
//                    for (Map.Entry<?, ?> entry : ((Map<?, ?>) originalItem).entrySet()) {
//                        assembleMap(String.valueOf(entry.getKey()), entry.getValue(), deleteArray);
//                    }
                    // TODO 暂不支持list 嵌套map
                } else if (childItemClassType == ClassTypeEnum.object) {
                    String primaryOriginal = findPrimaryValue(originalItem);
                    for (Object resultItem : resultRetainCollection) {
                        String primaryResult = findPrimaryValue(resultItem);
                        if (Objects.equals(primaryOriginal, primaryResult)) {
                            resultRetainCollection.remove(resultItem);
                            JsonObject itemObj = (JsonObject) createObject(originalItem, resultItem);
                            if (itemObj != null) {
                                itemObj.add("id", new JsonPrimitive(primaryOriginal));
                                updateArray.add(itemObj);
                            }
                            break;
                        }
                    }
                } else {
                    // 相同的基础数据是不变的数据，不需要记录
                }
            }
        }
        if (!insertArray.isEmpty()) {
            arrayJsonObj.add("insert", insertArray);
        }
        if (!updateArray.isEmpty()) {
            arrayJsonObj.add("update", updateArray);
        }
        if (!deleteArray.isEmpty()) {
            arrayJsonObj.add("delete", deleteArray);
        }
        return arrayJsonObj;
    }

    private static void assembleDeleteArray(Collection<?> originalCollection, JsonArray deleteArray) {
        for (Object originalItem : originalCollection) {
            ClassTypeEnum childItemClassType = ClassUtil.getClassType(originalItem);
            if (childItemClassType == ClassTypeEnum.list) {
                deleteArray.add(generateArray(null, originalItem));
            } else if (childItemClassType == ClassTypeEnum.map) {

            } else if (childItemClassType == ClassTypeEnum.object) {
                JsonObject itemObj = (JsonObject) createObject(originalItem, null);
                if (itemObj != null) {
                    String primaryValue = findPrimaryValue(originalItem);
                    if (primaryValue != null) {
                        itemObj.add("id", new JsonPrimitive(primaryValue));
                    }
                    deleteArray.add(itemObj);
                }
            } else {
                if (originalItem != null) {
                    if (originalItem instanceof Boolean) {
                        deleteArray.add(new JsonPrimitive((Boolean) originalItem));
                    } else {
                        deleteArray.add(new JsonPrimitive(String.valueOf(originalItem)));
                    }
                }
            }
        }
    }

    private static void assembleInsertArray(Collection<?> resultCollection, JsonArray insertArray) {
        for (Object resultItem : resultCollection) {
            ClassTypeEnum childItemClassType = ClassUtil.getClassType(resultItem);
            if (childItemClassType == ClassTypeEnum.list) {
                insertArray.add(generateArray(null, resultItem));
            } else if (childItemClassType == ClassTypeEnum.map) {

            } else if (childItemClassType == ClassTypeEnum.object) {
                JsonObject itemObj = (JsonObject) createObject(null, resultItem);
                if (itemObj != null) {
                    String primaryValue = findPrimaryValue(resultItem);
                    if (primaryValue != null) {
                        itemObj.add("id", new JsonPrimitive(primaryValue));
                    }
                    insertArray.add(itemObj);
                }
            } else {
                if (resultItem != null) {
                    if (resultItem instanceof Boolean) {
                        insertArray.add(new JsonPrimitive((Boolean) resultItem));
                    } else {
                        insertArray.add(new JsonPrimitive(String.valueOf(resultItem)));
                    }
                }
            }
        }
    }

    public static String findPrimaryValue(Object obj) {
        if (obj == null) {
            return null;
        }
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            LogPrimary annotation = field.getAnnotation(LogPrimary.class);
            if (annotation == null) {
                continue;
            }
            try {
                field.setAccessible(true);
                Object value = field.get(obj);
                if (value == null) {
                    return null;
                }
                return String.valueOf(value);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private static JsonObject generateObject(Object original, Object result) {
        JsonObject jsonObject = new JsonObject();
        Class<?> clazz;
        if (original != null) {
            clazz = original.getClass();
        } else {
            clazz = result.getClass();
        }
        List<Field> fields = getClassFields(clazz);

        for (Field field : fields) {
            if (field.getAnnotation(LogPrimary.class) != null) {
                continue;
            }
            field.setAccessible(true);
            Object originalValue = null;
            Object resultValue = null;
            if (original != null) {
                originalValue = getValue(original, field);
            }
            if (result != null) {
                resultValue = getValue(result, field);
            }
            JsonElement jsonElement = createObject(originalValue, resultValue);
            if (jsonElement != null) {
                jsonObject.add(field.getName(), jsonElement);
            }
        }
        String id = null;
        if (original != null) {
            id = findPrimaryValue(original);
        }
        if (id == null && result != null) {
            id = findPrimaryValue(result);
        }
        if (id != null) {
            jsonObject.addProperty("id", id);
        }

        return jsonObject;
    }

    private static JsonObject generatePrimitive(Object original, Object result) {
        if (Objects.equals(original, result)) {
            return null;
        }
        JsonObject jsonObject = new JsonObject();
        if (String.valueOf(result).equals("true") || String.valueOf(result).equals("false")) {
            if (original != null) {
                jsonObject.add("original", new JsonPrimitive((Boolean) original));
            }
            if (result != null) {
                jsonObject.add("result", new JsonPrimitive((Boolean) result));
            }
            return jsonObject;
        }
        if (original != null) {
            jsonObject.add("original", new JsonPrimitive(String.valueOf(original)));
        }
        if (result != null) {
            jsonObject.add("result", new JsonPrimitive(String.valueOf(result)));
        }
        return jsonObject;
    }


}
